/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package okhttp3;

import static okhttp3.internal.Util.closeQuietly;

import java.lang.ref.Reference;
import java.net.Socket;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;

import okhttp3.internal.Util;
import okhttp3.internal.connection.RealConnection;
import okhttp3.internal.connection.RouteDatabase;
import okhttp3.internal.connection.StreamAllocation;
import okhttp3.internal.platform.Platform;

/**频繁的进行建立Sokcet连接（TCP三次握手）和断开Socket（TCP四次分手）是非常消耗网络资源和浪费时间的，HTTP中的keepalive连接对于 降低延迟和提升速度有非常重要的作用。
复用连接就需要对连接进行管理，这里就引入了连接池的概念。
Okhttp支持5个并发KeepAlive，默认链路生命为5分钟(链路空闲后，保持存活的时间)，连接池有ConectionPool实现，对连接进行回收和管理
 * Manages reuse of HTTP and HTTP/2 connections for reduced network latency. HTTP requests that
 * share the same {@link Address} may share a {@link Connection}. This class implements the policy
 * of which connections to keep open for future use.
 */
public final class ConnectionPool {
  /**
   * Background threads are used to cleanup expired connections. There will be at most a single
   * thread running per connection pool. The thread pool executor permits the pool itself to be
   * garbage collected.
   * 连接池中维护了一个线程池，这个线程池中只开启了一个线程用来清理链接
   * 后台线程用于清理过期的连接。 每个连接池最多只能运行一个线程。 线程池执行器允许池本身被垃圾收集
   * 整个方法的流程如下所示：
1.查询此连接内部的StreamAllocation的引用数量。
2.标记空闲连接。
3.如果空闲连接超过5个或者keepalive时间大于5分钟，则将该连接清理掉。
4.返回此连接的到期时间，供下次进行清理。
5.全部都是活跃连接，5分钟时候再进行清理。
6.没有任何连接，跳出循环。
7.关闭连接，返回时间0，立即再次进行清理
   */
	private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
			Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
			new SynchronousQueue<Runnable>(), Util.threadFactory("OkHttp ConnectionPool", true));

	/** The maximum number of idle connections for each address. */
	// 最大的空闲连接数--每个地址的最大空闲连接数
	private final int maxIdleConnections;
	// 连接持续时间
	private final long keepAliveDurationNs;
	// corePoolSize=0 maximumPoolSize=Integer.MAX_VALUE 使用SynchronousQueue直接提交队列,
	// 清理连接，在线程池executor里调用
	private final Runnable cleanupRunnable = new Runnable() {
		@Override
		public void run() {
			while (true) {
				// 清除连接，并返回执行下一次cleanup的间隔时间，若返回-1，则表示不需要再清理
				long waitNanos = cleanup(System.nanoTime());
				if (waitNanos == -1)
					return;
				if (waitNanos > 0) {
					long waitMillis = waitNanos / 1000000L;
					waitNanos -= (waitMillis * 1000000L);
					synchronized (ConnectionPool.this) {
						try {
							// 等待间隔时间
							ConnectionPool.this.wait(waitMillis, (int) waitNanos);
						} catch (InterruptedException ignored) {
						}
					}
				}
			}
		}
	};

	// 维护了一个双端队列Deque来存储连接
	private final Deque<RealConnection> connections = new ArrayDeque<>();
	final RouteDatabase routeDatabase = new RouteDatabase();
	boolean cleanupRunning;

	/**
	 * Create a new connection pool with tuning parameters appropriate for a
	 * single-user application. The tuning parameters in this pool are subject to
	 * change in future OkHttp releases. Currently this pool holds up to 5 idle
	 * connections which will be evicted after 5 minutes of inactivity.
	 */
	public ConnectionPool() {
		// 默认每个地址的最大连接数是5个
		// 默认每个连接的存活时间为5分钟
		this(5, 5, TimeUnit.MINUTES);
	}

	public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
		this.maxIdleConnections = maxIdleConnections;
		this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

		// Put a floor on the keep alive duration, otherwise cleanup will spin loop.
		if (keepAliveDuration <= 0) {
			throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
		}
	}

	/** Returns the number of idle connections in the pool. */
	public synchronized int idleConnectionCount() {
		int total = 0;
		for (RealConnection connection : connections) {
			if (connection.allocations.isEmpty())
				total++;
		}
		return total;
	}

	/**
	 * Returns total number of connections in the pool. Note that prior to OkHttp
	 * 2.7 this included only idle connections and HTTP/2 connections. Since OkHttp
	 * 2.7 this includes all connections, both active and inactive. Use
	 * {@link #idleConnectionCount()} to count connections not currently in use.
	 */
	public synchronized int connectionCount() {
		return connections.size();
	}

	/**
	 * Returns a recycled connection to {@code address}, or null if no such
	 * connection exists. The route is null if the address has not yet been routed.
	 */
	@Nullable
	RealConnection get(Address address, StreamAllocation streamAllocation, Route route) {
		assert (Thread.holdsLock(this));
		// 如果connections不为空
		for (RealConnection connection : connections) {
			// 根据地址address和路由route判断链接是否为可用（或可复用）
			// 其实一个链接能否复用的条件我们能想到无外乎地址一样、端口一样、一条链接线路的负载不超过指定负载数等等这些条件。Okhttp对复用条件做了更多的限制，详见isEligible代码
			if (connection.isEligible(address, route)) {
				streamAllocation.acquire(connection, true);
				return connection;
			}
		}
		return null;
	}

	/**
	 * Replaces the connection held by {@code streamAllocation} with a shared
	 * connection if possible. This recovers when multiple multiplexed connections
	 * are created concurrently.
	 */
	@Nullable
	Socket deduplicate(Address address, StreamAllocation streamAllocation) {
		assert (Thread.holdsLock(this));
		// 遍历连接池中的connection
		for (RealConnection connection : connections) {
			// 判断连接是否可用、是否HTTP/2、并且和当期streamAllocation里的连接不是同一个
			if (connection.isEligible(address, null) && connection.isMultiplexed()
					&& connection != streamAllocation.connection()) {
				return streamAllocation.releaseAndAcquire(connection);
			}
		}
		return null;
	}

	// 将连接加入到双端队列
	// put方法在ConnectInterceptor-->intercept-->streamAllocation.newStream-->findHealthyConnection-->创建新链接后调用
	void put(RealConnection connection) {
		assert (Thread.holdsLock(this));
		// 没有任何连接时,cleanupRunning = false;
		// 即没当有任何链接时放入连接，才会去执行executor.execute(cleanupRunnable); 从而保证每个连接池最多只能运行一个线程
		if (!cleanupRunning) {
			cleanupRunning = true;
			executor.execute(cleanupRunnable);
		}
		connections.add(connection);
	}

	/**
	 * Notify this pool that {@code connection} has become idle. Returns true if the
	 * connection has been removed from the pool and should be closed.
	 */
	boolean connectionBecameIdle(RealConnection connection) {
		assert (Thread.holdsLock(this));
		if (connection.noNewStreams || maxIdleConnections == 0) {
			// 若noNewStreams为true或连接池最大容量为0，则移除该连接
			connections.remove(connection);
			return true;
		} else {
			// 唤醒清理线程，执行清理任务
			notifyAll(); // Awake the cleanup thread: we may have exceeded the idle connection limit.
			return false;
		}
	}

	/** Close and remove all idle connections in the pool. */
	public void evictAll() {
		List<RealConnection> evictedConnections = new ArrayList<>();
		synchronized (this) {
			for (Iterator<RealConnection> i = connections.iterator(); i.hasNext();) {
				RealConnection connection = i.next();
				if (connection.allocations.isEmpty()) {
					connection.noNewStreams = true;
					evictedConnections.add(connection);
					i.remove();
				}
			}
		}

		for (RealConnection connection : evictedConnections) {
			closeQuietly(connection.socket());
		}
	}

	/**
	 * Performs maintenance on this pool, evicting the connection that has been idle
	 * the longest if either it has exceeded the keep alive limit or the idle
	 * connections limit.
	 *
	 * <p>
	 * Returns the duration in nanos to sleep until the next scheduled call to this
	 * method. Returns -1 if no further cleanups are required.
	 */
	// 在从缓存的连接中取出连接来判断是否应该将其释放的时候使用到了两个变量 maxIdleConnections 和 keepAliveDurationNs，分别表示最大允许的闲置的连接的数量和连接允许存活的最长的时间。默认空闲连接最大数目为5个，keepalive 时间最长为5分钟。
	// 这个方法会对缓存中的连接进行遍历，以寻找一个闲置时间最长的连接，然后根据该连接的闲置时长和最大允许的连接数量等参数来决定是否应该清理该连接。
	// 同时注意方法的返回值是一个时间，如果闲置时间最长的连接仍然需要一段时间才能被清理的时候，会返回这段时间的时间差，然后会在这段时间之后再次对连接池进行清理
	long cleanup(long now) {
		int inUseConnectionCount = 0;
		int idleConnectionCount = 0;
		RealConnection longestIdleConnection = null;
		long longestIdleDurationNs = Long.MIN_VALUE;

		// Find either a connection to evict, or the time that the next eviction is due.
		synchronized (this) {
			// 遍历所有的连接，标记处于活跃的连接
			for (Iterator<RealConnection> i = connections.iterator(); i.hasNext();) {
				RealConnection connection = i.next();

				// If the connection is in use, keep searching.
				// 判断该连接是否非闲置连接: 查询此连接内部的StreanAllocation的引用数量
				if (pruneAndGetAllocationCount(connection, now) > 0) {
					// 在使用计数加1，走到这个分支的connection不会被清除
					inUseConnectionCount++;
					continue;
				}
				// 闲置计数加1
				idleConnectionCount++;

				// If the connection is ready to be evicted, we're done.
				// 都到这里说明该connection上已经没有活跃的StreanAllocation
				// 如果找到了一个可以被清理的连接，会尝试去寻找闲置时间最久的连接来释放
				long idleDurationNs = now - connection.idleAtNanos;
				// 比较闲置最久的连接，用longestIdleConnection临时保存
				if (idleDurationNs > longestIdleDurationNs) {
					longestIdleDurationNs = idleDurationNs;
					longestIdleConnection = connection;
				}
			}

			if (longestIdleDurationNs >= this.keepAliveDurationNs || idleConnectionCount > this.maxIdleConnections) {
				// We've found a connection to evict. Remove it from the list, then close it below (outside of the synchronized block).
				// 若闲置时间超时(默认5min)或闲置连接数超出允许最大闲置连接数(默认5个)，则移除该连接
				connections.remove(longestIdleConnection);
			} else if (idleConnectionCount > 0) {
				// A connection will be ready to evict soon.
				// 存在闲置连接，闲置最久连接仍未超时，则返回该连接剩余闲置时间，供下次进行清理（等会儿会将其清理掉，现在还不是时候）
				return keepAliveDurationNs - longestIdleDurationNs;
			} else if (inUseConnectionCount > 0) {
				// All connections are in use. It'll be at least the keep alive duration 'til we run again.
				// 不存在闲置连接，则返回一个闲置时间周期，5分钟时候再进行清理
				return keepAliveDurationNs;
			} else {
				// No connections, idle or in use.
				// 不存在连接，cleanupRunning标识置为false，返回-1，退出清理任务
				cleanupRunning = false;
				return -1;
			}
		}
		// 关闭需要清理的连接的socket，释放资源
		closeQuietly(longestIdleConnection.socket());

		// 立即清理闲置时间最长的连接
		return 0;
	}

	/**
	 * Prunes any leaked allocations and then returns the number of remaining live
	 * allocations on {@code connection}. Allocations are leaked if the connection
	 * is tracking them but the application code has abandoned them. Leak detection
	 * is imprecise and relies on garbage collection.
	 * 修剪任何泄漏的分配，然后返回{@code connection}上剩余的实时分配数量。 如果连接正在跟踪它们，但是应用程序代码已经放弃它们，则分配会泄漏。
	 * 泄漏检测不准确，依靠垃圾收集
	 */
	private int pruneAndGetAllocationCount(RealConnection connection, long now) {
		// 虚引用列表
		List<Reference<StreamAllocation>> references = connection.allocations;
		// 遍历虚引用列表
		for (int i = 0; i < references.size();) {
			Reference<StreamAllocation> reference = references.get(i);
			// 如果虚引用StreamAllocation正在被使用，则跳过进行下一次循环
			if (reference.get() != null) {
				// 引用计数
				i++;
				continue;
			}

			// 引用实际已被释放，正常情况下会从引用列表中移除该引用
			// We've discovered a leaked allocation. This is an application bug.
			StreamAllocation.StreamAllocationReference streamAllocRef = (StreamAllocation.StreamAllocationReference) reference;
			String message = "A connection to " + connection.route().address().url()
					+ " was leaked. Did you forget to close a response body?";
			Platform.get().logCloseableLeak(message, streamAllocRef.callStackTrace);
			// 否则移除该StreamAllocation引用
			references.remove(i);
			connection.noNewStreams = true;

			// If this was the last allocation, the connection is eligible for immediate
			// eviction.
			// 若该connection的引用列表为空，即为闲置连接，返回0
			if (references.isEmpty()) {
				// keepAliveDurationNs: 连接允许存活的最长的时间
				// idleAtNanos记录当前时间-闲置超时时间(默认5min)
				connection.idleAtNanos = now - keepAliveDurationNs;
				// 返回0
				return 0;
			}
		}
		// 返回引用个数，表示该连接上仍有多少个流在使用
		return references.size();
	}
}
